package demoMod.stsconsole.core;

import demoMod.stsconsole.ConsoleEntry;
import demoMod.stsconsole.enums.SubscriberPriority;
import demoMod.stsconsole.helpers.DamageInfo;
import demoMod.stsconsole.helpers.LocalizedStrings;
import demoMod.stsconsole.helpers.SubscriberReturnValueSaver;
import demoMod.stsconsole.powers.AbstractPower;
import demoMod.stsconsole.interfaces.*;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;

public abstract class AbstractCreature implements ReloadStringsSubscriber {
    public String id;
    public String name;
    protected int maxHealth;
    protected int currentHealth;
    protected int currentBlock;
    protected int gold;
    protected List<AbstractPower> powers = new ArrayList<>();
    public boolean isDead = false;
    public boolean isEscaped = false;
    public int lastDamageTaken = 0;

    public AbstractCreature(String id) {
        this.id = id;
        ConsoleEntry.addLater.add(this);
    }

    public List<AbstractPower> getPowers() {
        return powers;
    }

    public AbstractPower getPower(String id) {
        for (AbstractPower power : powers) {
            if (power.id.equals(id) && !power.isDone) {
                return power;
            }
        }
        return null;
    }

    public boolean hasPower(String id) {
        for (AbstractPower power : powers) {
            if (power.id.equals(id) && !power.isDone) {
                return true;
            }
        }
        return false;
    }

    public boolean hasPower(Class<? extends AbstractPower> powerCls) {
        for (AbstractPower power : powers) {
            if (powerCls.isInstance(power) && !power.isDone) {
                return true;
            }
        }
        return false;
    }

    public void applyPower(AbstractCreature source, AbstractPower power) {
        final List<Boolean> shouldApply = new ArrayList<>();
        ConsoleEntry.triggerSubscribers(
                PowerApplySubscriber.class,
                "onPowerApply",
                new Class[]{ AbstractPower.class, AbstractCreature.class },
                new Object[]{ power, this },
                (method, params, subscriber) -> {
                    try {
                        if (!(boolean) method.invoke(subscriber, params)) {
                            shouldApply.add(false);
                        }
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
        );
        List<String> commonStrings = LocalizedStrings.getCharacterStrings("AbstractPlayer");
        if (shouldApply.size() > 0) {
            source.talk(String.format(commonStrings.get(12), power.name, this.name));
            power.owner = null;
            power.onRemove();
            return;
        }
        if (!hasPower(power.id)) {
            this.powers.add(power);
            power.onInitialApplication();
        } else {
            AbstractPower power1 = getPower(power.id);
            power1.stackAmount(power.amount);
            power.owner = null;
            power.onRemove();
        }
        if (power.amount.length == 0) {
            this.talk(String.format(commonStrings.get(13), power.name));
        } else {
            this.talk(String.format(commonStrings.get(14), power.amount[0], power.name));
        }
    }

    public void removePower(String powerId) {
        if (hasPower(powerId)) {
            AbstractPower power = getPower(powerId);
            power.onRemove();
            powers.remove(power);
        }
    }

    public abstract void damage(DamageInfo info);

    public void heal(int healAmount) {
        if (healAmount > 0) {
            SubscriberReturnValueSaver saver = new SubscriberReturnValueSaver();
            saver.intValue = healAmount;
            ConsoleEntry.triggerSubscribers(HealSubscriber.class, "onHeal",
                    new Class[]{int.class}, new Object[]{healAmount},
                    (method, params, subscriber) -> {
                        try {
                            params[0] = method.invoke(subscriber, params);
                            saver.intValue = (int) params[0];
                        } catch (IllegalAccessException | InvocationTargetException e) {
                            e.printStackTrace();
                        }
                    }
            );
            this.currentHealth += saver.intValue;
            if (this.currentHealth > this.maxHealth) {
                this.currentHealth = this.maxHealth;
            }
            this.talk(String.format(LocalizedStrings.getCharacterStrings("AbstractPlayer").get(11), saver.intValue));
        }
    }

    public void gainMaxHealth(int amount) {
        gainMaxHealth(amount, true);
    }

    public void gainMaxHealth(int amount, boolean heal) {
        if (amount <= 0) return;
        this.maxHealth += amount;
        if (heal) {
            heal(amount);
        }
    }

    public void gainBlock(int blockAmount) {
        if (blockAmount > 0) {
            this.currentBlock += blockAmount;
            ConsoleEntry.triggerSubscribers(GainBlockSubscriber.class, "onGainBlock",
                    new Class[]{AbstractCreature.class, int.class}, new Object[]{this, blockAmount});
        }
    }

    public abstract void die();

    public abstract void escape();

    public boolean isDeadOrEscaped() {
        return this.isDead || this.isEscaped;
    }

    public int getMaxHealth() {
        return maxHealth;
    }

    public int getCurrentHealth() {
        return currentHealth;
    }

    public int getCurrentBlock() {
        return currentBlock;
    }

    public int getGold() {
        return gold;
    }

    public void gainGold(int amount) {
        if (amount <= 0) return;
        SubscriberReturnValueSaver saver = new SubscriberReturnValueSaver();
        saver.intValue = amount;
        ConsoleEntry.triggerSubscribers(GainGoldSubscriber.class, "onGainGold",
                new Class[]{int.class, AbstractCreature.class},
                new Object[]{amount, this},
                (method, params, subscriber) -> {
                    try {
                        params[0] = method.invoke(subscriber, params);
                        saver.intValue = (int) params[0];
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
        );
        amount = saver.intValue;
        this.gold += amount;
    }

    public void loseGold(int amount) {
        if (amount <= 0) return;
        ConsoleEntry.triggerSubscribers(LoseGoldSubscriber.class, "onLoseGold", new Class[]{int.class}, new Object[]{amount});
        this.gold -= amount;
    }

    public void onRemove() {
        ConsoleEntry.removeLater.add(this);
        for (AbstractPower power : powers) {
            power.owner = null;
            power.onRemove();
        }
    }

    public void talk(String msg) {
        System.out.printf("[%s] %s", this.name, msg);
        System.out.println();
    }

    @Override
    public int getPriority() {
        return SubscriberPriority.RELIC.getPriority() - 1;
    }

    public void decreaseMaxHealth(int amount) {
        if (maxHealth - amount < 1) {
            maxHealth = 1;
        } else {
            maxHealth -= amount;
            if (currentHealth > maxHealth) {
                currentHealth = maxHealth;
            }
        }
    }
}
